// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// SPDX-FileContributor: Leon Matthes <leon.matthes@kdab.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

#![deny(missing_docs)]

//! This crate and its associated crates provide a framework for generating QObjects from Rust.
//!
//! See the [book](https://kdab.github.io/cxx-qt/book/) for more information.

use std::ops::Deref;
use std::pin::Pin;
use std::{fs::File, io::Write, path::Path};

mod connection;
mod connectionguard;
mod qobject;
#[doc(hidden)]
pub mod signalhandler;
mod threading;

/// A procedural macro which generates a QObject for a struct inside a module.
///
/// # Example
///
/// ```rust
/// #[cxx_qt::bridge(namespace = "cxx_qt::my_object")]
/// mod qobject {
///     extern "RustQt" {
///         #[qobject]
///         # // Note that we can't use properties as this confuses the linker on Windows
///         type MyObject = super::MyObjectRust;
///
///         #[qinvokable]
///         fn invokable(self: &MyObject, a: i32, b: i32) -> i32;
///     }
/// }
///
/// #[derive(Default)]
/// pub struct MyObjectRust;
///
/// impl qobject::MyObject {
///     fn invokable(&self, a: i32, b: i32) -> i32 {
///         a + b
///     }
/// }
///
/// # // Note that we need a fake main for doc tests to build
/// # fn main() {
/// #   cxx_qt::init_crate!(cxx_qt);
/// # }
/// ```
pub use cxx_qt_macro::bridge;

/// Force a crate to be initialized
pub use cxx_qt_macro::init_crate;

/// Force a QML module with the given URI to be initialized
pub use cxx_qt_macro::init_qml_module;

/// A macro which describes that a struct should be made into a QObject.
///
/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition.
///
/// # Example
///
/// ```rust
/// #[cxx_qt::bridge]
/// mod my_object {
///     extern "RustQt" {
///         #[qobject]
///         # // Note that we can't use properties as this confuses the linker on Windows
///         type MyObject = super::MyObjectRust;
///     }
/// }
///
/// #[derive(Default)]
/// pub struct MyObjectRust;
///
/// # // Note that we need a fake main for doc tests to build
/// # fn main() {
/// #   cxx_qt::init_crate!(cxx_qt);
/// # }
/// ```
///
/// You can also specify a custom base class by using `#[base = QStringListModel]`, you must then use CXX to add any includes needed.
///
/// # Example
///
/// ```rust
/// #[cxx_qt::bridge]
/// mod my_object {
///     extern "RustQt" {
///         #[qobject]
///         #[base = QStringListModel]
///         # // Note that we can't use properties as this confuses the linker on Windows
///         type MyModel = super::MyModelRust;
///     }
///
///     unsafe extern "C++" {
///         include!(<QtCore/QStringListModel>);
///         type QStringListModel;
///     }
/// }
///
/// #[derive(Default)]
/// pub struct MyModelRust;
///
/// # // Note that we need a fake main for doc tests to build
/// # fn main() {
/// #   cxx_qt::init_crate!(cxx_qt);
/// # }
/// ```
pub use cxx_qt_macro::qobject;
pub use qobject::QObject;

pub use connection::{ConnectionType, QMetaObjectConnection};
pub use connectionguard::QMetaObjectConnectionGuard;
pub use threading::{CxxQtThread, ThreadingQueueError};

// Export static assertions that can then be used in cxx-qt-gen generation
//
// These are currently used to ensure that CxxQtSignalHandler has the right size
#[doc(hidden)]
pub use static_assertions;

/// This trait is automatically implemented for all QObject types generated by CXX-Qt.
/// It provides information about the inner Rust struct that is wrapped by the QObject, as well as the methods
/// that Cxx-Qt will generate for the QObject.
pub trait CxxQtType {
    /// The Rust type that this QObject is wrapping.
    type Rust;

    /// Retrieve an immutable reference to the Rust struct backing this C++ object
    fn rust(&self) -> &Self::Rust;

    /// Retrieve a mutable reference to the Rust struct backing this C++ object
    fn rust_mut(self: core::pin::Pin<&mut Self>) -> core::pin::Pin<&mut Self::Rust>;
}

/// This trait indicates that the object implements threading and has a method which returns a [CxxQtThread].
///
/// The QObjects generated by CXX-Qt are neither [`Send`](https://doc.rust-lang.org/std/marker/trait.Send.html) nor [`Sync`](https://doc.rust-lang.org/std/marker/trait.Sync.html).
/// Therefore they may not be passed between threads nor accessed from multiple threads.
///
/// To achieve safe multi-threading on the Rust side we use an [CxxQtThread].
/// A [CxxQtThread] represents a reference to the Qt thread that the QObject lives in.
/// When a new Rust thread is started (e.g. in an invokable) the [CxxQtThread] can be moved into the thread to later update the QObject in a thread safe manner.
///
/// # Example
///
/// ```rust,ignore
/// # // FIXME: test doesn't link correctly on Windows
/// #[cxx_qt::bridge]
/// mod qobject {
///     extern "RustQt" {
///         #[qobject]
///         type MyStruct = super::MyStructRust;
///
///        #[qinvokable]
///         fn say_hello(self: Pin<&mut MyStruct>);
///     }
///
///     impl cxx_qt::Threading for MyStruct {}
/// }
///
/// use cxx_qt::Threading;
///
/// #[derive(Default)]
/// pub struct MyStructRust;
///
/// impl qobject::MyStruct {
///     pub fn say_hello(self: core::pin::Pin<&mut Self>) {
///         let qt_thread = self.qt_thread();
///
///         // Start a background thread that doesn't block the invokable
///         std::thread::spawn(move || {
///             std::thread::sleep(std::time::Duration::from_secs(1));
///
///             // Say hello on the Qt event loop
///             qt_thread.queue(|_| {
///                 println!("Hello");
///             }).unwrap();
///         });
///     }
/// }
///
/// # // Note that we need a fake main function for doc tests to build.
/// # fn main() {}
/// ```
pub trait Threading: Sized {
    #[doc(hidden)]
    type BoxedQueuedFn;
    #[doc(hidden)]
    type ThreadingTypeId;

    /// Create an instance of a [CxxQtThread]
    ///
    /// This allows for queueing closures onto the Qt event loop from a background thread.
    fn qt_thread(&self) -> CxxQtThread<Self>;

    #[doc(hidden)]
    fn is_destroyed(cxx_qt_thread: &CxxQtThread<Self>) -> bool;

    #[doc(hidden)]
    fn queue<F>(cxx_qt_thread: &CxxQtThread<Self>, f: F) -> Result<(), ThreadingQueueError>
    where
        F: FnOnce(core::pin::Pin<&mut Self>),
        F: Send + 'static;

    #[doc(hidden)]
    fn threading_clone(cxx_qt_thread: &CxxQtThread<Self>) -> CxxQtThread<Self>;

    #[doc(hidden)]
    fn threading_drop(cxx_qt_thread: &mut CxxQtThread<Self>);
}

/// This trait is automatically implemented by CXX-Qt and you most likely do not need to manually implement it.
/// Allows upcasting to either [QObject] or the provided base class of a type.
/// Will not be implemented if no types inherit from [QObject] or have the `#[base = T]` attribute.
pub trait Upcast<T> {
    #[doc(hidden)]
    /// # Safety
    ///
    /// Internal function, Should probably not be implemented manually unless you're absolutely sure you need it.
    /// Automatically available for types in RustQt blocks in [cxx_qt::bridge](bridge)s.
    /// Upcasts a pointer to `Self` to a pointer to the base class `T`.
    /// > Note: Internal implementation uses `static_cast`.
    unsafe fn upcast_ptr(this: *const Self) -> *const T;

    #[doc(hidden)]
    /// # Safety
    ///
    /// Internal function, Should probably not be implemented manually unless you're absolutely sure you need it.
    /// Automatically available for types in RustQt blocks in [cxx_qt::bridge](bridge)s.
    /// Downcasts a pointer to base class `T` to a pointer to `Self`.
    /// Return a null pointer if `Self` is not actually a child of base.
    /// > Note: Internal implementation uses `dynamic_cast`.
    unsafe fn from_base_ptr(base: *const T) -> *const Self;

    /// Upcast a reference to self to a reference to the base class
    fn upcast(&self) -> &T {
        let ptr = self as *const Self;
        unsafe {
            let base = Self::upcast_ptr(ptr);
            &*base
        }
    }

    /// Upcast a mutable reference to sell to a mutable reference to the base class
    fn upcast_mut(&mut self) -> &mut T {
        let ptr = self as *const Self;
        unsafe {
            let base = Self::upcast_ptr(ptr) as *mut T;
            &mut *base
        }
    }

    /// Upcast a pinned mutable reference to self to a pinned mutable reference to the base class
    fn upcast_pin(self: Pin<&mut Self>) -> Pin<&mut T> {
        let this = self.deref() as *const Self;
        unsafe {
            let base = Self::upcast_ptr(this) as *mut T;
            Pin::new_unchecked(&mut *base)
        }
    }
}

impl<T> Upcast<T> for T {
    unsafe fn upcast_ptr(this: *const Self) -> *const Self {
        this
    }

    unsafe fn from_base_ptr(base: *const T) -> *const Self {
        base
    }

    fn upcast(&self) -> &Self {
        self
    }

    fn upcast_mut(&mut self) -> &mut Self {
        self
    }

    fn upcast_pin(self: Pin<&mut Self>) -> Pin<&mut Self> {
        self
    }
}

/// This trait is automatically implemented by CXX-Qt and you most likely do not need to manually implement it.
/// Trait for downcasting to a subclass, provided the subclass implements [Upcast] to this type.
/// Returns `None` in cases where `Sub` isn't a child class of `Self`.
pub trait Downcast: Sized {
    /// Try to downcast to a subclass of this type, given that the subclass upcasts to this type
    fn downcast<Sub: Upcast<Self>>(&self) -> Option<&Sub> {
        unsafe {
            let ptr = Sub::from_base_ptr(self as *const Self);
            if ptr.is_null() {
                None
            } else {
                Some(&*ptr)
            }
        }
    }

    /// Try to downcast mutably to a subclass of this, given that the subclass upcasts to this type
    fn downcast_mut<Sub: Upcast<Self>>(&mut self) -> Option<&mut Sub> {
        unsafe {
            let ptr = Sub::from_base_ptr(self as *const Self) as *mut Sub;
            if ptr.is_null() {
                None
            } else {
                Some(&mut *ptr)
            }
        }
    }

    /// Try to downcast a pin to a pinned subclass of this, given that the subclass upcasts to this type
    fn downcast_pin<Sub: Upcast<Self>>(self: Pin<&mut Self>) -> Option<Pin<&mut Sub>> {
        let this = self.deref() as *const Self;
        unsafe {
            let ptr = Sub::from_base_ptr(this) as *mut Sub;
            if ptr.is_null() {
                None
            } else {
                Some(Pin::new_unchecked(&mut *ptr))
            }
        }
    }
}

/// Automatic implementation of Downcast for any applicable types
impl<T: Sized> Downcast for T {}

/// This trait can be implemented on any [CxxQtType] to define a
/// custom constructor in C++ for the QObject.
///
/// The `Arguments` must be a tuple of CXX types that will be the arguments to the constructor in C++.
///
/// If this trait is implemented for a given [CxxQtType], it must also be declared inside the
/// [cxx_qt::bridge](bridge) macro.
/// See the example below.
///
/// Note that declaring an implementation of this trait will stop CXX-Qt from generating a default constructor.
/// Therefore an implementation of [Default] is no longer required for the Rust type.
///
/// # Minimal Example
///
/// ```rust
/// #[cxx_qt::bridge]
/// mod qobject {
///     extern "RustQt" {
///         #[qobject]
///         type MyStruct = super::MyStructRust;
///     }
///
///     // Declare that we want to use a custom constructor
///     // Note that the arguments must be a tuple of CXX types.
///     // Any associated types that aren't included here are assumed to be `()`.
///     impl cxx_qt::Constructor<(i32, String), NewArguments=(i32, String)> for MyStruct {}
/// }
///
/// // Struct without `Default` implementation
/// pub struct MyStructRust {
///     pub integer: i32,
///     pub string: String
/// }
///
/// impl cxx_qt::Constructor<(i32, String)> for qobject::MyStruct {
///     type BaseArguments = (); // Will be passed to the base class constructor
///     type InitializeArguments = (); // Will be passed to the "initialize" function
///     type NewArguments = (i32, String); // Will be passed to the "new" function
///
///     fn route_arguments(args: (i32, String)) -> (
///         Self::NewArguments,
///         Self::BaseArguments,
///         Self::InitializeArguments
///     ) {
///         (args, (), ())
///     }
///
///     fn new((integer, string): (i32, String)) -> MyStructRust {
///         MyStructRust {
///             integer,
///             string
///         }
///     }
/// }
///
/// # // Note that we need a fake main function for doc tests to build.
/// # fn main() {
/// #    cxx_qt::init_crate!(cxx_qt);
/// # }
/// ```
///
/// # Pseudo Code for generated C++ Constructor
/// You can imagine this trait as creating a constructor roughly like this:
/// ```cpp
/// class MyCxxQtType : public QObject {
///     public:
///         MyCxxQtType(Arguments... args)
///             : QObject(Constructor::route_arguments(args).BaseArguments)
///             , m_rust(Constructor::new(Constructor::route_arguments(args).NewArguments))
///         {
///             Constructor::initialize(*this, Constructor::route_arguments(args).InitializeArguments);
///         }
/// }
/// ```
/// Note that in reality, `route_arguments` will only be called once and all arguments
/// will be moved, never copied.
///
/// # Initializing the QObject
///
/// In addition to running code before constructing the inner Rust struct, it may be useful to run code from the context of the QObject itself (i.e. inside the Constructor implementation).
///
/// The `initialize` function can be used to run code inside a constructor.
/// It is given a pinned mutable self reference to the QObject and the list of `InitializeArguments`.
///
/// ## Using the `Initialize` trait
///
/// The QML engine creates QML elements using their default constructor, so for most QML types only the `initialize` part of the constructor is of interest.
/// To reduce the boilerplate of this use-case, CXX-Qt provides the [Initialize] trait.
///
/// If a QObject implements the `Initialize` trait, and the inner Rust struct is [Default]-constructible it will automatically implement `cxx_qt::Constructor<()>`.
/// Additionally, implementing `impl cxx_qt::Initialize` will act as shorthand for `cxx_qt::Constructor<()>`.
pub trait Constructor<Arguments>: CxxQtType {
    /// The arguments that are passed to the [`new()`](Self::new) function to construct the inner Rust struct.
    /// This must be a tuple of CXX compatible types.
    ///
    /// This way QObjects can be constructed that need additional arguments for constructing the
    /// inner Rust type.
    type NewArguments;
    /// The arguments that should be passed to the constructor of the base class.
    /// This must be a tuple of CXX compatible types.
    type BaseArguments;
    /// The arguments that should be used to initialize the QObject in the [`initialize()`](Self::initialize) function.
    /// This must be a tuple of CXX compatible types.
    type InitializeArguments;

    /// This function is called by CXX-Qt to route the arguments to the correct places.
    ///
    /// Using this function, you can split up the arguments required by the QObject constructor
    /// without additional copies.
    ///
    #[allow(unused_variables)]
    fn route_arguments(
        arguments: Arguments,
    ) -> (
        Self::NewArguments,
        Self::BaseArguments,
        Self::InitializeArguments,
    );

    /// This function is called to construct the inner Rust struct of the CXX-Qt QObject.
    /// You can use this to construct Rust structs that do not provide a [Default] implementation.
    fn new(arguments: Self::NewArguments) -> <Self as CxxQtType>::Rust;

    /// This function is called to initialize the QObject.
    /// After the members of the QObject is initialized, this function is called.
    /// This is equivalent to the body of the constructor in C++.
    ///
    /// # Default
    /// By default, this function does nothing
    #[allow(unused_variables)]
    fn initialize(self: core::pin::Pin<&mut Self>, arguments: Self::InitializeArguments) {
        // By default, do nothing
    }
}

/// This trait can be implemented on any [CxxQtType] to automatically define a default constructor
/// that calls the `initialize` function after constructing a default Rust struct.
///
/// Ensure that the `impl cxx_qt::Constructor<()> for ... {}` is declared inside the CXX-Qt bridge.
/// Alternatively, using `impl cxx_qt::Initialize for ... {}` acts as shorthand for the above line.
///
/// # Example
///
/// ```rust,ignore
/// # // FIXME: test doesn't link correctly on Windows
/// #[cxx_qt::bridge]
/// mod qobject {
///     extern "RustQt" {
///         #[qobject]
///         #[qproperty(i32, integer)]
///         type MyStruct = super::MyStructRust;
///     }
///
///     // Remember to tell the bridge about the default constructor
///     impl cxx_qt::Constructor<()> for MyStruct {}
///     // or
///     impl cxx_qt::Initialize for MyStruct {}
/// }
///
/// // Make sure the inner Rust struct implements `Default`
/// #[derive(Default)]
/// pub struct MyStructRust {
///     integer: i32,
/// }
///
/// impl cxx_qt::Initialize for qobject::MyStruct {
///     fn initialize(self: core::pin::Pin<&mut Self>) {
///         self.on_integer_changed(|qobject| {
///             println!("New integer value: {}", qobject.integer);
///         }).release();
///     }
/// }
///
/// # // Note that we need a fake main function for doc tests to build.
/// # fn main() {}
/// ```
// TODO: Once the QObject type is available in the cxx-qt crate, also auto-generate a default
// constructor that takes QObject and passes it to the parent.
pub trait Initialize: CxxQtType {
    /// This function is called to initialize the QObject after construction.
    fn initialize(self: core::pin::Pin<&mut Self>);
}

impl<T> Constructor<()> for T
where
    T: Initialize,
    T::Rust: Default,
{
    type NewArguments = ();
    type BaseArguments = ();
    type InitializeArguments = ();

    fn new(_arguments: ()) -> <Self as CxxQtType>::Rust {
        <Self as CxxQtType>::Rust::default()
    }

    fn route_arguments(
        _arguments: (),
    ) -> (
        Self::NewArguments,
        Self::BaseArguments,
        Self::InitializeArguments,
    ) {
        ((), (), ())
    }

    fn initialize(self: core::pin::Pin<&mut Self>, _arguments: Self::InitializeArguments) {
        Self::initialize(self);
    }
}

#[doc(hidden)]
// Write the cxx-qt headers to the specified directory.
pub fn write_headers(directory: impl AsRef<Path>) {
    let directory = directory.as_ref();
    std::fs::create_dir_all(directory).expect("Could not create cxx-qt header directory");
    // Note ensure that the build script is consistent with files that are copied
    for (file_contents, file_name) in [
        (include_str!("../include/connection.h"), "connection.h"),
        (
            include_str!("../include/signalhandler.h"),
            "signalhandler.h",
        ),
        (include_str!("../include/thread.h"), "thread.h"),
        (include_str!("../include/threading.h"), "threading.h"),
        (include_str!("../include/type.h"), "type.h"),
    ] {
        // Note that we do not need rerun-if-changed for these files
        // as include_str causes a rerun when the header changes
        // and the files are always written to the target.
        let h_path = format!("{}/{file_name}", directory.display());
        let mut header = File::create(h_path).expect("Could not create cxx-qt header");
        write!(header, "{file_contents}").expect("Could not write cxx-qt header");
    }
}
